package common;

import java.io.Serializable;

import model.entity.Product;

/**
 * The size of a {@link Product}. Can be any combination of positive (not including 0) amounts and
 * units, unless the unit is <code>Unit.COUNT</code>, when the amount must be 1.
 * 
 * {@invariant <code>getAmount() > 0</code>}
 * 
 * @author Ryan Cheatham
 * 
 *         {@invariant <code>getUnit() == Unit.COUNT => getAmount == 1</code>}
 */
public class Size implements Serializable
{
  private static final long serialVersionUID = 3977247812991870411L;

  /**
   * Validates a given size object.
   * 
   * @param size
   *          The size to validate
   * @return a result, indicating whether the size is valid.
   */
  public static Result validateSize(Size size)
  {
    if (size == null)
      return new Result(false, "Invalid size - null");
    else
      return validateSize(size.getUnit(), size.getAmount());
  }

  /**
   * Validates whether a given unit and amount are valid size attributes.
   * 
   * @param unit
   *          The unit to validate.
   * @param amount
   *          The amount to validate.
   * @return a result, indicating whether the unit-amount pair is a valid size object.
   */
  public static Result validateSize(Unit unit, float amount)
  {
    if (unit == Unit.COUNT && amount != 1)
      return new Result(false, "Invalid size - if unit is Unit.COUNT, amount must equal 1.");
    else
      return new Result(true);
  }

  private float amount;
  private Unit  unit;

  /**
   * Constructs a size where the unit is Unit.COUNT and the amount is 1.
   * 
   * {@post <code>getUnit() == Unit.COUNT</code>} {@post <code>getAmount() == 1</code>}
   */
  public Size()
  {
    unit = Unit.COUNT;
    amount = 1;
  }

  /**
   * Constructs a size where the unit and amount are the given values.
   * 
   * @param amount
   *          The amount of units this size represents.
   * @param unit
   *          The unit of this size.
   * 
   *          {@pre <code>amount > 0</code>} {@pre <code>unit == Unit.COUNT => amount == 1</code>}
   */
  public Size(float amount, Unit unit)
  {
    this.amount = amount;
    this.unit = unit;
  }

  /**
   * Returns the numeric amount of this size.
   */
  public float getAmount()
  {
    return amount;
  }

  /**
   * Validates the given amount. If the amount is invalid, a negative Result will be returned.
   * 
   * @param amount
   *          The amount to validate.
   * @return a result, whose status indicates the validity of the given amount.
   */
  public Result validateUpdateAmount(float amount)
  {
    if (unit == Unit.COUNT && amount != 1)
      return new Result(false, "Amount can only be one if the unit is Unit.COUNT");
    else
      return new Result(true);
  }

  /**
   * Validates the given amount, and if it is valid, updates this size to reflect the given amount.
   * If the amount is invalid, a negative Result is returned.
   * 
   * @param amount
   *          The new amount.
   * @return a result, whose status indicates whether the operation succeeded.
   */
  public Result updateAmount(float amount)
  {
    Result result = validateUpdateAmount(amount);
    if (result.getStatus())
      this.amount = amount;

    return result;
  }

  /**
   * Returns the unit of this size.
   */
  public Unit getUnit()
  {
    return unit;
  }

  /**
   * Validates the given unit. If this size can be set to use the given unit, a positive result is
   * returned.
   * 
   * @param unit
   *          The unit to validate.
   * @return a result, whose status indicates the validity of the given unit in relation to this
   *         size.
   */
  public Result validateUpdateUnit(Unit unit)
  {
    return new Result(true);
  }

  /**
   * Validates the given unit, and if it is valid, updates this size to reflect the given unit. If
   * the unit is invalid, a negative Result is returned.
   * 
   * @param unit
   *          The new unit.
   * @return a result, whose status indicates whether the operation succeeded.
   */
  public Result updateUnit(Unit unit)
  {
    Result result = validateUpdateUnit(unit);
    if (result.getStatus())
      this.unit = unit;

    return result;
  }
}
